export module format;

import <string>;
import <format>;
import <iostream>;

//-----------------------------------------------------------------------------
class KeyValue
{
public:
	KeyValue(std::string_view key, int value)
		: m_key(key),
		m_value(value)
	{
	}

	const std::string &getKey() const
	{
		return m_key;
	}

	int getValue() const
	{
		return m_value;
	}

private:
	std::string m_key;
	int m_value;
};

template<>
class std::formatter<KeyValue>
{
public:
	constexpr auto parse(auto &ctx)
	{
		auto iter{ ctx.begin() };
		const auto end{ ctx.end() };

		if (iter == end || *iter == '}')
		{
			m_outputType = OutputType::KeyAndValue;

			return iter;
		}

		switch (*iter)
		{
			case 'k':
			{
				m_outputType = OutputType::KeyOnly;

				break;
			}
			case 'v':
			{
				m_outputType = OutputType::ValueOnly;

				break;
			}
			case 'a':
			{
				m_outputType = OutputType::KeyAndValue;

				break;
			}
			default:
			{
				throw std::format_error("Invalid keyValue format specifier.");
				break;
			}
		}

		++iter;

		if (iter != end && *iter != '}')
		{
			throw std::format_error("Invalid keyValue format specifier.");
		}

		return iter;
	}

	auto format(const KeyValue &kv, auto &ctx) const
	{
		switch (m_outputType)
		{
			case OutputType::KeyOnly:
			{
				return std::format_to(ctx.out(), "{}", kv.getKey());
			}
			case OutputType::ValueOnly:
			{
				return std::format_to(ctx.out(), "{}", kv.getValue());
			}
			default:
			{
				return std::format_to(ctx.out(), "{} : {}", kv.getKey(), kv.getValue());
			}
		}
	}

private:
	enum class OutputType
	{
		KeyOnly,
		ValueOnly,
		KeyAndValue
	};

	OutputType m_outputType{ OutputType::KeyAndValue };
};

namespace format_test
{
	void test()
	{
		KeyValue kv{ "key", 42 };
		std::cout << std::format("{}", kv) << std::endl;
		std::cout << std::format("{:a}", kv) << std::endl;
		std::cout << std::format("{:k}", kv) << std::endl;
		std::cout << std::format("{:v}", kv) << std::endl;

		try
		{
			std::cout << std::format("{:abc}", kv) << std::endl;
		}
		catch (const std::format_error &e)
		{
			std::cout << e.what() << std::endl;
		}
	}
}
